/*-------------------------------------------------------------------------
 *
 * be-secure-openssl.c
 *	  后端中OpenSSL支持的函数。
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/libpq/be-secure-openssl.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include <sys/stat.h>
#include <signal.h>
#include <fcntl.h>
#include <ctype.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netdb.h>
#include <netinet/in.h>
#ifdef HAVE_NETINET_TCP_H
#include <netinet/tcp.h>
#include <arpa/inet.h>
#endif

#include "libpq/libpq.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "storage/fd.h"
#include "storage/latch.h"
#include "tcop/tcopprot.h"
#include "utils/memutils.h"

/*
 * 这些与SSL相关的#includes必须在所有系统提供的头文件之后。
 * 这确保OpenSSL可以通过#undef'冲突的宏来处理与Windows的
 * <wincrypt.h>的冲突。 （我们不直接包含<wincrypt.h>，但其他一些Windows头文件确实包含。）
 */
#include "common/openssl.h"
#include <openssl/conf.h>
#include <openssl/dh.h>
#ifndef OPENSSL_NO_ECDH
#include <openssl/ec.h>
#endif
#include <openssl/x509v3.h>


/* 默认初始化钩子可以被共享库重写 */
static void fc_default_openssl_tls_init(SSL_CTX *fc_context, bool fc_isServerStart);
openssl_tls_init_hook_typ openssl_tls_init_hook = fc_default_openssl_tls_init;
#ifdef FDDGM
static void fc_default_openssl_tlcp_enc_init(SSL_CTX *fc_context, bool fc_isServerStart);
openssl_tls_init_hook_typ openssl_tlcp_enc_init_hook = fc_default_openssl_tlcp_enc_init;

static int	fc_tlcp_enc_external_passwd_cb(char *fc_buf, int fc_size, int fc_rwflag, void *fc_userdata);
#endif

static int	fc_my_sock_read(BIO *fc_h, char *fc_buf, int fc_size);
static int	fc_my_sock_write(BIO *fc_h, const char *fc_buf, int fc_size);
static BIO_METHOD *fc_my_BIO_s_socket(void);
static int	fc_my_SSL_set_fd(Port *fc_port, int fc_fd);

static DH  *fc_load_dh_file(char *fc_filename, bool fc_isServerStart);
static DH  *fc_load_dh_buffer(const char *, size_t);
static int	fc_ssl_external_passwd_cb(char *fc_buf, int fc_size, int fc_rwflag, void *fc_userdata);
static int	fc_dummy_ssl_passwd_cb(char *fc_buf, int fc_size, int fc_rwflag, void *fc_userdata);
static int	fc_verify_cb(int, X509_STORE_CTX *);
static void fc_info_cb(const SSL *fc_ssl, int fc_type, int fc_args);
static bool fc_initialize_dh(SSL_CTX *fc_context, bool fc_isServerStart);
static bool fc_initialize_ecdh(SSL_CTX *fc_context, bool fc_isServerStart);
static const char *fc_SSLerrmessage(unsigned long fc_ecode);

static char *fc_X509_NAME_to_cstring(X509_NAME *fc_name);

static SSL_CTX *SSL_context = NULL;
static bool SSL_initialized = false;
static bool dummy_ssl_passwd_cb_called = false;
static bool ssl_is_server_start;

static int	fc_ssl_protocol_version_to_openssl(int fc_v);
static const char *fc_ssl_protocol_version_to_string(int fc_v);

/* ------------------------------------------------------------ */
/*						 公共接口						*/
/* ------------------------------------------------------------ */

int be_tls_init(bool fc_isServerStart)
{
	SSL_CTX    *fc_context;
	int			fc_ssl_ver_min = -1;
	int			fc_ssl_ver_max = -1;

	/* 这些内容只需要做一次。 */
	if (!SSL_initialized)
	{
#ifdef HAVE_OPENSSL_INIT_SSL
		OPENSSL_init_ssl(OPENSSL_INIT_LOAD_CONFIG, NULL);
#else
		OPENSSL_config(NULL);
		SSL_library_init();
		SSL_load_error_strings();
#endif
		SSL_initialized = true;
	}

	/*
	 * 创建一个新的SSL上下文，在其中加载所有的配置
	 * 设置。如果我们在过程中失败，可以通过
	 * 释放这个上下文来避免内存泄漏；我们在结束之前不会将其设为活动。
	 *
	 * 我们使用SSLv23_method()是因为它可以协商使用
	 * 最高的相互支持的协议版本，而像
	 * TLSv1_2_method()这样的替代方法仅允许一个特定版本。请注意，我们实际上不
	 * 允许SSL v2或v3，只允许TLS协议（见下文）。
	 */
#ifdef FDDGM
	if(EnableTLCP)
	{
		fc_context = SSL_CTX_new(TLS_server_method());
	}
	else
#endif
	fc_context = SSL_CTX_new(SSLv23_method());
	if (!fc_context)
	{
		ereport(fc_isServerStart ? FATAL : LOG,
				(errmsg("could not create SSL context: %s",
						fc_SSLerrmessage(ERR_get_error()))));
		goto error;
	}

	/*
	 * 禁用 OpenSSL 的移动写缓冲区完整性检查，因为它会导致
	 * 在非阻塞发送情况下的不必要失败。
	 */
	SSL_CTX_set_mode(fc_context, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);

	/*
	 * 调用初始化钩子（通常是设置密码回调）
	 */
	(*openssl_tls_init_hook) (fc_context, fc_isServerStart);

	/* 被回调使用 */
	ssl_is_server_start = fc_isServerStart;

	/*
	 * 加载并验证服务器的证书和私钥
	 */
#ifdef FDDGM
	if(EnableTLCP)
	{
		if (SSL_CTX_use_gm_certificate_file(fc_context, ssl_cert_file, SSL_FILETYPE_PEM, SSL_USAGE_SIG) != 1)
		{
			ereport(fc_isServerStart ? FATAL : LOG,
					(errcode(ERRCODE_CONFIG_FILE_ERROR),
					 errmsg("could not load server signed certificate file \"%s\": %s",
							ssl_cert_file, fc_SSLerrmessage(ERR_get_error()))));
			goto error;
		}
		
		if (SSL_CTX_use_gm_certificate_file(fc_context, ssl_enccert_file, SSL_FILETYPE_PEM, SSL_USAGE_ENC) != 1)
		{
			ereport(fc_isServerStart ? FATAL : LOG,
					(errcode(ERRCODE_CONFIG_FILE_ERROR),
					 errmsg("could not load server encryption  certificate file \"%s\": %s",
							ssl_enccert_file, fc_SSLerrmessage(ERR_get_error()))));
			goto error;
		}
	}
	else
#endif
	if (SSL_CTX_use_certificate_chain_file(fc_context, ssl_cert_file) != 1)
	{
		ereport(fc_isServerStart ? FATAL : LOG,
				(errcode(ERRCODE_CONFIG_FILE_ERROR),
				 errmsg("could not load server certificate file \"%s\": %s",
						ssl_cert_file, fc_SSLerrmessage(ERR_get_error()))));
		goto error;
	}

	if (!check_ssl_key_file_permissions(ssl_key_file, fc_isServerStart))
		goto error;

	/*
	 * 好的，尝试加载私钥文件。
	 */
	dummy_ssl_passwd_cb_called = false;

#ifdef FDDGM
	if(EnableTLCP)
	{
		if (SSL_CTX_use_gm_PrivateKey_file(fc_context, ssl_key_file, SSL_FILETYPE_PEM, SSL_USAGE_SIG) != 1)
		{
			if (dummy_ssl_passwd_cb_called)
				ereport(fc_isServerStart ? FATAL : LOG,
						(errcode(ERRCODE_CONFIG_FILE_ERROR),
						 errmsg("signed private key file \"%s\" cannot be reloaded because it requires a passphrase",
								ssl_key_file)));
			else
				ereport(fc_isServerStart ? FATAL : LOG,
						(errcode(ERRCODE_CONFIG_FILE_ERROR),
						 errmsg("could not load signed private key file \"%s\": %s",
								ssl_key_file, fc_SSLerrmessage(ERR_get_error()))));
			goto error;
		}
		(*openssl_tlcp_enc_init_hook) (fc_context, fc_isServerStart);//初始化tlcp 加密证书密钥被加密时，用于解密的命令，签名证书密钥解密的命令是pg自带的	openssl_tls_init_hook	
		if (SSL_CTX_use_gm_PrivateKey_file(fc_context, ssl_enckey_file, SSL_FILETYPE_PEM, SSL_USAGE_ENC) != 1)
		{
			if (dummy_ssl_passwd_cb_called)
				ereport(fc_isServerStart ? FATAL : LOG,
						(errcode(ERRCODE_CONFIG_FILE_ERROR),
						 errmsg("encryption private key file \"%s\" cannot be reloaded because it requires a passphrase",
								ssl_enckey_file)));
			else
				ereport(fc_isServerStart ? FATAL : LOG,
						(errcode(ERRCODE_CONFIG_FILE_ERROR),
						 errmsg("could not load encryption private key file \"%s\": %s",
								ssl_enckey_file, fc_SSLerrmessage(ERR_get_error()))));
			goto error;
		}
	}
	else
#endif
	if (SSL_CTX_use_PrivateKey_file(fc_context,
									ssl_key_file,
									SSL_FILETYPE_PEM) != 1)
	{
		if (dummy_ssl_passwd_cb_called)
			ereport(fc_isServerStart ? FATAL : LOG,
					(errcode(ERRCODE_CONFIG_FILE_ERROR),
					 errmsg("private key file \"%s\" cannot be reloaded because it requires a passphrase",
							ssl_key_file)));
		else
			ereport(fc_isServerStart ? FATAL : LOG,
					(errcode(ERRCODE_CONFIG_FILE_ERROR),
					 errmsg("could not load private key file \"%s\": %s",
							ssl_key_file, fc_SSLerrmessage(ERR_get_error()))));
		goto error;
	}

#ifdef FDDGM
    //TLCP在加载私钥时已经检查了
	if ( !EnableTLCP && SSL_CTX_check_private_key(fc_context) != 1)
#else
	if (SSL_CTX_check_private_key(context) != 1)
	{
		ereport(fc_isServerStart ? FATAL : LOG,
				(errcode(ERRCODE_CONFIG_FILE_ERROR),
				 errmsg("check of private key failed: %s",
						fc_SSLerrmessage(ERR_get_error()))));
		goto error;
	}
#endif

	if (ssl_min_protocol_version)
	{
		fc_ssl_ver_min = fc_ssl_protocol_version_to_openssl(ssl_min_protocol_version);

		if (fc_ssl_ver_min == -1)
		{
			ereport(fc_isServerStart ? FATAL : LOG,
			/*- translator: 第一个%s是GUC选项名称，第二个%s是其值 */
					(errmsg("\"%s\" setting \"%s\" not supported by this build",
							"ssl_min_protocol_version",
							GetConfigOption("ssl_min_protocol_version",
											false, false))));
			goto error;
		}

		if (!SSL_CTX_set_min_proto_version(fc_context, fc_ssl_ver_min))
		{
			ereport(fc_isServerStart ? FATAL : LOG,
					(errmsg("could not set minimum SSL protocol version")));
			goto error;
		}
	}

	if (ssl_max_protocol_version)
	{
		fc_ssl_ver_max = fc_ssl_protocol_version_to_openssl(ssl_max_protocol_version);

		if (fc_ssl_ver_max == -1)
		{
			ereport(fc_isServerStart ? FATAL : LOG,
			/*- translator: 第一个%s是GUC选项名称，第二个%s是其值 */
					(errmsg("\"%s\" setting \"%s\" not supported by this build",
							"ssl_max_protocol_version",
							GetConfigOption("ssl_max_protocol_version",
											false, false))));
			goto error;
		}

		if (!SSL_CTX_set_max_proto_version(fc_context, fc_ssl_ver_max))
		{
			ereport(fc_isServerStart ? FATAL : LOG,
					(errmsg("could not set maximum SSL protocol version")));
			goto error;
		}
	}

	/* 检查最小/最大协议的兼容性 */
	if (ssl_min_protocol_version &&
		ssl_max_protocol_version)
	{
		/*
		 * 不需要检查每个协议号的无效值（-1），因为上面的代码
		 * 已经生成了错误。
		 */
		if (fc_ssl_ver_min > fc_ssl_ver_max)
		{
			ereport(fc_isServerStart ? FATAL : LOG,
					(errmsg("could not set SSL protocol version range"),
					 errdetail("\"%s\" cannot be higher than \"%s\"",
							   "ssl_min_protocol_version",
							   "ssl_max_protocol_version")));
			goto error;
		}
	}

	/*
	 * 不允许SSL会话票据。 OpenSSL为TLSv1.3使用有状态和无状态
	 * 票据，为TLSv1.2使用无状态票据。自0.9.8f以来，SSL_OP_NO_TICKET
	 * 可用，但只会关闭无状态票据。为了关闭有状态票据，我们需要SSL_CTX_set_num_tickets，
	 * 该函数自OpenSSL 1.1.1以来可用。LibreSSL 3.5.4（来自OpenBSD
	 * 7.1）引入了该API以兼容，但根本不支持会话
	 * 票据，因此在此处无效。
	 */
#ifdef HAVE_SSL_CTX_SET_NUM_TICKETS
	SSL_CTX_set_num_tickets(fc_context, 0);
#endif
	SSL_CTX_set_options(fc_context, SSL_OP_NO_TICKET);

	/* 也不允许SSL会话缓存 */
	SSL_CTX_set_session_cache_mode(fc_context, SSL_SESS_CACHE_OFF);

	/* 不允许SSL压缩 */
	SSL_CTX_set_options(fc_context, SSL_OP_NO_COMPRESSION);

#ifdef SSL_OP_NO_RENEGOTIATION

	/*
	 * 不允许SSL重新协商，自1.1.0h以来可用的选项。仅涉及TLSv1.2及之前的协议版本，
	 * 因为TLSv1.3不支持重新协商。
	 */
	SSL_CTX_set_options(fc_context, SSL_OP_NO_RENEGOTIATION);
#endif

	/* 设置临时DH和ECDH密钥 */
	if (!fc_initialize_dh(fc_context, fc_isServerStart))
		goto error;
	if (!fc_initialize_ecdh(fc_context, fc_isServerStart))
		goto error;

	/* 设置允许的密码列表 */
	if (SSL_CTX_set_cipher_list(fc_context, SSLCipherSuites) != 1)
	{
		ereport(fc_isServerStart ? FATAL : LOG,
				(errcode(ERRCODE_CONFIG_FILE_ERROR),
				 errmsg("could not set the cipher list (no valid ciphers available)")));
		goto error;
	}

	/* 让服务器选择顺序 */
	if (SSLPreferServerCiphers)
		SSL_CTX_set_options(fc_context, SSL_OP_CIPHER_SERVER_PREFERENCE);

	/*
	 * 加载CA存储，以便在需要时可以验证客户端证书。
	 */
	if (ssl_ca_file[0])
	{
		STACK_OF(X509_NAME) * fc_root_cert_list;

		if (SSL_CTX_load_verify_locations(fc_context, ssl_ca_file, NULL) != 1 ||
			(fc_root_cert_list = SSL_load_client_CA_file(ssl_ca_file)) == NULL)
		{
			ereport(fc_isServerStart ? FATAL : LOG,
					(errcode(ERRCODE_CONFIG_FILE_ERROR),
					 errmsg("could not load root certificate file \"%s\": %s",
							ssl_ca_file, fc_SSLerrmessage(ERR_get_error()))));
			goto error;
		}

		/*
		 * 告诉OpenSSL将我们信任的根证书列表发送给客户端
		 * 在CertificateRequests中。这让具有密钥存储的客户端选择
		 * 适当的客户端证书发送给我们。此外，这确保
		 * SSL上下文将“拥有”root_cert_list，并记住在不再需要时
		 * 释放它。
		 */
		SSL_CTX_set_client_CA_list(fc_context, fc_root_cert_list);

		/*
		 * 始终请求SSL客户端证书，但如果未提供则不失败。
		 * 我们可能会在稍后根据在pg_hba.conf中的内容失败这样的连接。
		 */
		SSL_CTX_set_verify(fc_context,
						   (SSL_VERIFY_PEER |
							SSL_VERIFY_CLIENT_ONCE),
						   fc_verify_cb);
	}

	/*----------
	 * 加载证书撤销列表（CRL）。
	 * http://searchsecurity.techtarget.com/sDefinition/0,,sid14_gci803160,00.html
	 *----------
	 */
	if (ssl_crl_file[0] || ssl_crl_dir[0])
	{
		X509_STORE *fc_cvstore = SSL_CTX_get_cert_store(fc_context);

		if (fc_cvstore)
		{
			/* 设置标志以检查完整的 CRL 链 */
			if (X509_STORE_load_locations(fc_cvstore,
										  ssl_crl_file[0] ? ssl_crl_file : NULL,
										  ssl_crl_dir[0] ? ssl_crl_dir : NULL)
				== 1)
			{
				X509_STORE_set_flags(fc_cvstore,
									 X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL);
			}
			else if (ssl_crl_dir[0] == 0)
			{
				ereport(fc_isServerStart ? FATAL : LOG,
						(errcode(ERRCODE_CONFIG_FILE_ERROR),
						 errmsg("could not load SSL certificate revocation list file \"%s\": %s",
								ssl_crl_file, fc_SSLerrmessage(ERR_get_error()))));
				goto error;
			}
			else if (ssl_crl_file[0] == 0)
			{
				ereport(fc_isServerStart ? FATAL : LOG,
						(errcode(ERRCODE_CONFIG_FILE_ERROR),
						 errmsg("could not load SSL certificate revocation list directory \"%s\": %s",
								ssl_crl_dir, fc_SSLerrmessage(ERR_get_error()))));
				goto error;
			}
			else
			{
				ereport(fc_isServerStart ? FATAL : LOG,
						(errcode(ERRCODE_CONFIG_FILE_ERROR),
						 errmsg("could not load SSL certificate revocation list file \"%s\" or directory \"%s\": %s",
								ssl_crl_file, ssl_crl_dir,
								fc_SSLerrmessage(ERR_get_error()))));
				goto error;
			}
		}
	}

	/*
	 * 成功！ 替换任何现有的SSL_context。
	 */
	if (SSL_context)
		SSL_CTX_free(SSL_context);

	SSL_context = fc_context;

	/*
	 * 设置标志以记住CA存储是否已加载到SSL_context中。
	 */
	if (ssl_ca_file[0])
		ssl_loaded_verify_locations = true;
	else
		ssl_loaded_verify_locations = false;

	return 0;

	/* 通过释放工作上下文进行清理。 */
error:
	if (fc_context)
		SSL_CTX_free(fc_context);
	return -1;
}

void be_tls_destroy(void)
{
	if (SSL_context)
		SSL_CTX_free(SSL_context);
	SSL_context = NULL;
	ssl_loaded_verify_locations = false;
}

int be_tls_open_server(Port *fc_port)
{
	int			fc_r;
	int			fc_err;
	int			fc_waitfor;
	unsigned long fc_ecode;
	bool		fc_give_proto_hint;

	Assert(!fc_port->ssl);
	Assert(!fc_port->peer);

	if (!SSL_context)
	{
		ereport(COMMERROR,
				(errcode(ERRCODE_PROTOCOL_VIOLATION),
				 errmsg("could not initialize SSL connection: SSL context not set up")));
		return -1;
	}

	/* 设置调试/信息回调 */
	SSL_CTX_set_info_callback(SSL_context, fc_info_cb);

	if (!(fc_port->ssl = SSL_new(SSL_context)))
	{
		ereport(COMMERROR,
				(errcode(ERRCODE_PROTOCOL_VIOLATION),
				 errmsg("could not initialize SSL connection: %s",
						fc_SSLerrmessage(ERR_get_error()))));
		return -1;
	}
	if (!fc_my_SSL_set_fd(fc_port, fc_port->sock))
	{
		ereport(COMMERROR,
				(errcode(ERRCODE_PROTOCOL_VIOLATION),
				 errmsg("could not set SSL socket: %s",
						fc_SSLerrmessage(ERR_get_error()))));
		return -1;
	}
	fc_port->ssl_in_use = true;

aloop:

	/*
	 * 通过清除线程的OpenSSL错误队列准备调用SSL_get_error()。
	 * 通常，在尝试TLS/SSL I/O操作之前，当前线程的错误队列必须为空，
	 * 否则SSL_get_error()将无法可靠工作。扩展可能未能在
	 * 另一个调用OpenSSL I/O例程后清除每线程错误队列。
	 */
	errno = 0;
	ERR_clear_error();
	fc_r = SSL_accept(fc_port->ssl);
	if (fc_r <= 0)
	{
		fc_err = SSL_get_error(fc_port->ssl, fc_r);

		/*
		 * 后端的OpenSSL其他客户端可能未能调用
		 * ERR_get_error()，但我们始终会调用，以免对
		 * 不以防御性方式调用ERR_clear_error()的OpenSSL客户端造成问题。
		 * 通过现在调用确保这一点。SSL_get_error()依赖于
		 * OpenSSL每线程错误队列的完整性，因此这是
		 * 可调用ERR_get_error()的最早时间点。
		 */
		fc_ecode = ERR_get_error();
		switch (fc_err)
		{
			case SSL_ERROR_WANT_READ:
			case SSL_ERROR_WANT_WRITE:
				/* 不允许在连接建立期间 */
				Assert(!fc_port->noblock);

				/*
				 * 在这里无需关心超时/中断。在这一点上，
				 * authentication_timeout仍然使用
				 * StartupPacketTimeoutHandler()，这会直接退出。
				 */
				if (fc_err == SSL_ERROR_WANT_READ)
					fc_waitfor = WL_SOCKET_READABLE | WL_EXIT_ON_PM_DEATH;
				else
					fc_waitfor = WL_SOCKET_WRITEABLE | WL_EXIT_ON_PM_DEATH;

				(void) WaitLatchOrSocket(MyLatch, fc_waitfor, fc_port->sock, 0,
										 WAIT_EVENT_SSL_OPEN_SERVER);
				goto aloop;
			case SSL_ERROR_SYSCALL:
				if (fc_r < 0 && errno != 0)
					ereport(COMMERROR,
							(errcode_for_socket_access(),
							 errmsg("could not accept SSL connection: %m")));
				else
					ereport(COMMERROR,
							(errcode(ERRCODE_PROTOCOL_VIOLATION),
							 errmsg("could not accept SSL connection: EOF detected")));
				break;
			case SSL_ERROR_SSL:
				switch (ERR_GET_REASON(fc_ecode))
				{
						/*
						 * UNSUPPORTED_PROTOCOL、WRONG_VERSION_NUMBER和
						 * TLSV1_ALERT_PROTOCOL_VERSION已被观察到
						 * 在尝试与旧的OpenSSL
						 * 库进行通信时，或当客户端和服务器指定
						 * 不相交的协议范围时。NO_PROTOCOLS_AVAILABLE
						 * 会在本地配置错误时发生（尽管我们进行了检查，
						 * 如果openssl.cnf
						 * 注入了我们未考虑的限制则可能发生）。不太清楚
						 * 什么会导致OpenSSL返回此处列出的其他
						 * 代码，但关于协议版本的提示似乎适用于所有。
						 */
					case SSL_R_NO_PROTOCOLS_AVAILABLE:
					case SSL_R_UNSUPPORTED_PROTOCOL:
					case SSL_R_BAD_PROTOCOL_VERSION_NUMBER:
					case SSL_R_UNKNOWN_PROTOCOL:
					case SSL_R_UNKNOWN_SSL_VERSION:
					case SSL_R_UNSUPPORTED_SSL_VERSION:
					case SSL_R_WRONG_SSL_VERSION:
					case SSL_R_WRONG_VERSION_NUMBER:
					case SSL_R_TLSV1_ALERT_PROTOCOL_VERSION:
#ifdef SSL_R_VERSION_TOO_HIGH
					case SSL_R_VERSION_TOO_HIGH:
					case SSL_R_VERSION_TOO_LOW:
#endif
						fc_give_proto_hint = true;
						break;
					default:
						fc_give_proto_hint = false;
						break;
				}
				ereport(COMMERROR,
						(errcode(ERRCODE_PROTOCOL_VIOLATION),
						 errmsg("could not accept SSL connection: %s",
								fc_SSLerrmessage(fc_ecode)),
						 fc_give_proto_hint ?
						 errhint("This may indicate that the client does not support any SSL protocol version between %s and %s.",
								 ssl_min_protocol_version ?
								 fc_ssl_protocol_version_to_string(ssl_min_protocol_version) :
								 MIN_OPENSSL_TLS_VERSION,
								 ssl_max_protocol_version ?
								 fc_ssl_protocol_version_to_string(ssl_max_protocol_version) :
								 MAX_OPENSSL_TLS_VERSION) : 0));
				break;
			case SSL_ERROR_ZERO_RETURN:
				ereport(COMMERROR,
						(errcode(ERRCODE_PROTOCOL_VIOLATION),
						 errmsg("could not accept SSL connection: EOF detected")));
				break;
			default:
				ereport(COMMERROR,
						(errcode(ERRCODE_PROTOCOL_VIOLATION),
						 errmsg("unrecognized SSL error code: %d",
								fc_err)));
				break;
		}
		return -1;
	}

	/* 获取客户端证书（如果可用）。 */
	fc_port->peer = SSL_get_peer_certificate(fc_port->ssl);

	/* 并从中提取公共名称和区分名称。 */
	fc_port->peer_cn = NULL;
	fc_port->peer_dn = NULL;
	fc_port->peer_cert_valid = false;
	if (fc_port->peer != NULL)
	{
		int			fc_len;
		X509_NAME  *fc_x509name = X509_get_subject_name(fc_port->peer);
		char	   *fc_peer_dn;
		BIO		   *fc_bio = NULL;
		BUF_MEM    *fc_bio_buf = NULL;

		fc_len = X509_NAME_get_text_by_NID(fc_x509name, NID_commonName, NULL, 0);
		if (fc_len != -1)
		{
			char	   *fc_peer_cn;

			fc_peer_cn = MemoryContextAlloc(TopMemoryContext, fc_len + 1);
			fc_r = X509_NAME_get_text_by_NID(fc_x509name, NID_commonName, fc_peer_cn,
										  fc_len + 1);
			fc_peer_cn[fc_len] = '\0';
			if (fc_r != fc_len)
			{
				/* 不应该发生 */
				pfree(fc_peer_cn);
				return -1;
			}

			/*
			 * 拒绝证书公共名称中的嵌入NULL，以防止
			 * 像CVE-2009-4034这样的攻击。
			 */
			if (fc_len != strlen(fc_peer_cn))
			{
				ereport(COMMERROR,
						(errcode(ERRCODE_PROTOCOL_VIOLATION),
						 errmsg("SSL certificate's common name contains embedded null")));
				pfree(fc_peer_cn);
				return -1;
			}

			fc_port->peer_cn = fc_peer_cn;
		}

		fc_bio = BIO_new(BIO_s_mem());
		if (!fc_bio)
		{
			if (fc_port->peer_cn != NULL)
			{
				pfree(fc_port->peer_cn);
				fc_port->peer_cn = NULL;
			}
			return -1;
		}

		/*
		 * RFC2253 是最接近被接受的 DN 标准格式的东西。我们已经记录了如何从证书中生成这种格式。它使用逗号而不是斜杠作为分隔符，这使得正则表达式匹配变得稍微容易一些。还要注意，它以相反的顺序打印主题字段。
		 */
		if (X509_NAME_print_ex(fc_bio, fc_x509name, 0, XN_FLAG_RFC2253) == -1 ||
			BIO_get_mem_ptr(fc_bio, &fc_bio_buf) <= 0)
		{
			BIO_free(fc_bio);
			if (fc_port->peer_cn != NULL)
			{
				pfree(fc_port->peer_cn);
				fc_port->peer_cn = NULL;
			}
			return -1;
		}
		fc_peer_dn = MemoryContextAlloc(TopMemoryContext, fc_bio_buf->length + 1);
		memcpy(fc_peer_dn, fc_bio_buf->data, fc_bio_buf->length);
		fc_len = fc_bio_buf->length;
		BIO_free(fc_bio);
		fc_peer_dn[fc_len] = '\0';
		if (fc_len != strlen(fc_peer_dn))
		{
			ereport(COMMERROR,
					(errcode(ERRCODE_PROTOCOL_VIOLATION),
					 errmsg("SSL certificate's distinguished name contains embedded null")));
			pfree(fc_peer_dn);
			if (fc_port->peer_cn != NULL)
			{
				pfree(fc_port->peer_cn);
				fc_port->peer_cn = NULL;
			}
			return -1;
		}

		fc_port->peer_dn = fc_peer_dn;

		fc_port->peer_cert_valid = true;
	}

	return 0;
}

void be_tls_close(Port *fc_port)
{
	if (fc_port->ssl)
	{
		SSL_shutdown(fc_port->ssl);
		SSL_free(fc_port->ssl);
		fc_port->ssl = NULL;
		fc_port->ssl_in_use = false;
	}

	if (fc_port->peer)
	{
		X509_free(fc_port->peer);
		fc_port->peer = NULL;
	}

	if (fc_port->peer_cn)
	{
		pfree(fc_port->peer_cn);
		fc_port->peer_cn = NULL;
	}

	if (fc_port->peer_dn)
	{
		pfree(fc_port->peer_dn);
		fc_port->peer_dn = NULL;
	}
}

ssize_t be_tls_read(Port *fc_port, void *fc_ptr, size_t fc_len, int *fc_waitfor)
{
	ssize_t		fc_n;
	int			fc_err;
	unsigned long fc_ecode;

	errno = 0;
	ERR_clear_error();
	fc_n = SSL_read(fc_port->ssl, fc_ptr, fc_len);
	fc_err = SSL_get_error(fc_port->ssl, fc_n);
	fc_ecode = (fc_err != SSL_ERROR_NONE || fc_n < 0) ? ERR_get_error() : 0;
	switch (fc_err)
	{
		case SSL_ERROR_NONE:
			/* 一切正常 */
			break;
		case SSL_ERROR_WANT_READ:
			*fc_waitfor = WL_SOCKET_READABLE;
			errno = EWOULDBLOCK;
			fc_n = -1;
			break;
		case SSL_ERROR_WANT_WRITE:
			*fc_waitfor = WL_SOCKET_WRITEABLE;
			errno = EWOULDBLOCK;
			fc_n = -1;
			break;
		case SSL_ERROR_SYSCALL:
			/* 将 errno 的值报告给调用者 */
			if (fc_n != -1 || errno == 0)
			{
				errno = ECONNRESET;
				fc_n = -1;
			}
			break;
		case SSL_ERROR_SSL:
			ereport(COMMERROR,
					(errcode(ERRCODE_PROTOCOL_VIOLATION),
					 errmsg("SSL error: %s", fc_SSLerrmessage(fc_ecode))));
			errno = ECONNRESET;
			fc_n = -1;
			break;
		case SSL_ERROR_ZERO_RETURN:
			/* 连接被对等方干净地关闭 */
			fc_n = 0;
			break;
		default:
			ereport(COMMERROR,
					(errcode(ERRCODE_PROTOCOL_VIOLATION),
					 errmsg("unrecognized SSL error code: %d",
							fc_err)));
			errno = ECONNRESET;
			fc_n = -1;
			break;
	}

	return fc_n;
}

ssize_t be_tls_write(Port *fc_port, void *fc_ptr, size_t fc_len, int *fc_waitfor)
{
	ssize_t		fc_n;
	int			fc_err;
	unsigned long fc_ecode;

	errno = 0;
	ERR_clear_error();
	fc_n = SSL_write(fc_port->ssl, fc_ptr, fc_len);
	fc_err = SSL_get_error(fc_port->ssl, fc_n);
	fc_ecode = (fc_err != SSL_ERROR_NONE || fc_n < 0) ? ERR_get_error() : 0;
	switch (fc_err)
	{
		case SSL_ERROR_NONE:
			/* 一切正常 */
			break;
		case SSL_ERROR_WANT_READ:
			*fc_waitfor = WL_SOCKET_READABLE;
			errno = EWOULDBLOCK;
			fc_n = -1;
			break;
		case SSL_ERROR_WANT_WRITE:
			*fc_waitfor = WL_SOCKET_WRITEABLE;
			errno = EWOULDBLOCK;
			fc_n = -1;
			break;
		case SSL_ERROR_SYSCALL:

			/*
			 * 将 errno 的值报告给调用者。然而，如果 errno 仍为零，则假设这是一种读取 EOF 的情况，并报告 ECONNRESET。（这似乎是可能的，因为 SSL_write 也可以进行读取。）
			 */
			if (fc_n != -1 || errno == 0)
			{
				errno = ECONNRESET;
				fc_n = -1;
			}
			break;
		case SSL_ERROR_SSL:
			ereport(COMMERROR,
					(errcode(ERRCODE_PROTOCOL_VIOLATION),
					 errmsg("SSL error: %s", fc_SSLerrmessage(fc_ecode))));
			errno = ECONNRESET;
			fc_n = -1;
			break;
		case SSL_ERROR_ZERO_RETURN:

			/*
			 * SSL 连接已关闭，将报告交给调用者
			 */
			errno = ECONNRESET;
			fc_n = -1;
			break;
		default:
			ereport(COMMERROR,
					(errcode(ERRCODE_PROTOCOL_VIOLATION),
					 errmsg("unrecognized SSL error code: %d",
							fc_err)));
			errno = ECONNRESET;
			fc_n = -1;
			break;
	}

	return fc_n;
}

/* ------------------------------------------------------------ */
/*						内部函数						*/
/* ------------------------------------------------------------ */

/*
 * 私有替代 BIO：这是通过使用 send() 和 recv() 来进行发送和接收。这样我们可以在调用 recv() 时启用和禁用中断。我们不能在 OpenSSL 大部分运行时发生中断，因为它使用 malloc() 和可能其他非重入的 libc 设施。我们还需要直接调用 send() 和 recv()，以便它通过 Win32 的套接字/信号层。
 *
 * 这些函数在 OpenSSL 的标准套接字 BIO 上建立了紧密的模型；请参见 OpenSSL 的 crypto/bio/bss_sock.c 中的 sock_read() 和 sock_write()。
 * XXX OpenSSL 1.0.1e 考虑了许多其他的错误代码，而不仅仅是 EINTR 作为重试的理由；我们是否需要采用它们的逻辑？
 */

static BIO_METHOD *my_bio_methods = NULL;

static int fc_my_sock_read(BIO *fc_h, char *fc_buf, int fc_size)
{
	int			fc_res = 0;

	if (fc_buf != NULL)
	{
		fc_res = secure_raw_read(((Port *) BIO_get_app_data(fc_h)), fc_buf, fc_size);
		BIO_clear_retry_flags(fc_h);
		if (fc_res <= 0)
		{
			/* 如果我们被中断，告知调用者重试 */
			if (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN)
			{
				BIO_set_retry_read(fc_h);
			}
		}
	}

	return fc_res;
}

static int fc_my_sock_write(BIO *fc_h, const char *fc_buf, int fc_size)
{
	int			fc_res = 0;

	fc_res = secure_raw_write(((Port *) BIO_get_app_data(fc_h)), fc_buf, fc_size);
	BIO_clear_retry_flags(fc_h);
	if (fc_res <= 0)
	{
		/* 如果我们被中断，告知调用者重试 */
		if (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN)
		{
			BIO_set_retry_write(fc_h);
		}
	}

	return fc_res;
}

static BIO_METHOD *
fc_my_BIO_s_socket(void)
{
	if (!my_bio_methods)
	{
		BIO_METHOD *fc_biom = (BIO_METHOD *) BIO_s_socket();
#ifdef HAVE_BIO_METH_NEW
		int			fc_my_bio_index;

		fc_my_bio_index = BIO_get_new_index();
		if (fc_my_bio_index == -1)
			return NULL;
		fc_my_bio_index |= (BIO_TYPE_DESCRIPTOR | BIO_TYPE_SOURCE_SINK);
		my_bio_methods = BIO_meth_new(fc_my_bio_index, "PostgreSQL backend socket");
		if (!my_bio_methods)
			return NULL;
		if (!BIO_meth_set_write(my_bio_methods, fc_my_sock_write) ||
			!BIO_meth_set_read(my_bio_methods, fc_my_sock_read) ||
			!BIO_meth_set_gets(my_bio_methods, BIO_meth_get_gets(fc_biom)) ||
			!BIO_meth_set_puts(my_bio_methods, BIO_meth_get_puts(fc_biom)) ||
			!BIO_meth_set_ctrl(my_bio_methods, BIO_meth_get_ctrl(fc_biom)) ||
			!BIO_meth_set_create(my_bio_methods, BIO_meth_get_create(fc_biom)) ||
			!BIO_meth_set_destroy(my_bio_methods, BIO_meth_get_destroy(fc_biom)) ||
			!BIO_meth_set_callback_ctrl(my_bio_methods, BIO_meth_get_callback_ctrl(fc_biom)))
		{
			BIO_meth_free(my_bio_methods);
			my_bio_methods = NULL;
			return NULL;
		}
#else
		my_bio_methods = malloc(sizeof(BIO_METHOD));
		if (!my_bio_methods)
			return NULL;
		memcpy(my_bio_methods, fc_biom, sizeof(BIO_METHOD));
		my_bio_methods->bread = fc_my_sock_read;
		my_bio_methods->bwrite = fc_my_sock_write;
#endif
	}
	return my_bio_methods;
}

/* 这应该与 OpenSSL 的 SSL_set_fd 完全匹配，除了使用我的 BIO */
static int fc_my_SSL_set_fd(Port *fc_port, int fc_fd)
{
	int			fc_ret = 0;
	BIO		   *fc_bio;
	BIO_METHOD *fc_bio_method;

	fc_bio_method = fc_my_BIO_s_socket();
	if (fc_bio_method == NULL)
	{
		SSLerr(SSL_F_SSL_SET_FD, ERR_R_BUF_LIB);
		goto err;
	}
	fc_bio = BIO_new(fc_bio_method);

	if (fc_bio == NULL)
	{
		SSLerr(SSL_F_SSL_SET_FD, ERR_R_BUF_LIB);
		goto err;
	}
	BIO_set_app_data(fc_bio, fc_port);

	BIO_set_fd(fc_bio, fc_fd, BIO_NOCLOSE);
	SSL_set_bio(fc_port->ssl, fc_bio, fc_bio);
	fc_ret = 1;
err:
	return fc_ret;
}

/*
 * 加载预计算的 DH 参数。
 *
 * 为了防止“降级”攻击，我们进行了一些检查，以验证 DBA 生成的 DH 参数文件包含我们所期望的内容。
 */
static DH  *
fc_load_dh_file(char *fc_filename, bool fc_isServerStart)
{
	FILE	   *fc_fp;
	DH		   *fc_dh = NULL;
	int			fc_codes;

	/* 尝试打开文件。如果文件不存在，这不是错误。 */
	if ((fc_fp = AllocateFile(fc_filename, "r")) == NULL)
	{
		ereport(fc_isServerStart ? FATAL : LOG,
				(errcode_for_file_access(),
				 errmsg("could not open DH parameters file \"%s\": %m",
						fc_filename)));
		return NULL;
	}

	fc_dh = PEM_read_DHparams(fc_fp, NULL, NULL, NULL);
	FreeFile(fc_fp);

	if (fc_dh == NULL)
	{
		ereport(fc_isServerStart ? FATAL : LOG,
				(errcode(ERRCODE_CONFIG_FILE_ERROR),
				 errmsg("could not load DH parameters file: %s",
						fc_SSLerrmessage(ERR_get_error()))));
		return NULL;
	}

	/* 确保 DH 参数是可用的 */
	if (DH_check(fc_dh, &fc_codes) == 0)
	{
		ereport(fc_isServerStart ? FATAL : LOG,
				(errcode(ERRCODE_CONFIG_FILE_ERROR),
				 errmsg("invalid DH parameters: %s",
						fc_SSLerrmessage(ERR_get_error()))));
		DH_free(fc_dh);
		return NULL;
	}
	if (fc_codes & DH_CHECK_P_NOT_PRIME)
	{
		ereport(fc_isServerStart ? FATAL : LOG,
				(errcode(ERRCODE_CONFIG_FILE_ERROR),
				 errmsg("invalid DH parameters: p is not prime")));
		DH_free(fc_dh);
		return NULL;
	}
	if ((fc_codes & DH_NOT_SUITABLE_GENERATOR) &&
		(fc_codes & DH_CHECK_P_NOT_SAFE_PRIME))
	{
		ereport(fc_isServerStart ? FATAL : LOG,
				(errcode(ERRCODE_CONFIG_FILE_ERROR),
				 errmsg("invalid DH parameters: neither suitable generator or safe prime")));
		DH_free(fc_dh);
		return NULL;
	}

	return fc_dh;
}

/*
 * 加载硬编码的 DH 参数。
 *
 * 如果无法从指定文件加载 DH 参数，我们可以加载与后端一起提供的硬编码 DH 参数以防止问题。
 */
static DH  *
fc_load_dh_buffer(const char *fc_buffer, size_t fc_len)
{
	BIO		   *fc_bio;
	DH		   *fc_dh = NULL;

	fc_bio = BIO_new_mem_buf(unconstify(char *, fc_buffer), fc_len);
	if (fc_bio == NULL)
		return NULL;
	fc_dh = PEM_read_bio_DHparams(fc_bio, NULL, NULL, NULL);
	if (fc_dh == NULL)
		ereport(DEBUG2,
				(errmsg_internal("DH load buffer: %s",
								 fc_SSLerrmessage(ERR_get_error()))));
	BIO_free(fc_bio);

	return fc_dh;
}

/*
 * 使用 ssl_passphrase_command 的口令收集回调
 */
static int fc_ssl_external_passwd_cb(char *fc_buf, int fc_size, int fc_rwflag, void *fc_userdata)
{
	/* 与 OpenSSL 内部使用的提示相同 */
	const char *fc_prompt = "Enter PEM pass phrase:";

	Assert(fc_rwflag == 0);

#ifndef FDDGM
	return run_ssl_passphrase_command(fc_prompt, ssl_is_server_start, fc_buf, fc_size);
#else
    return run_ssl_passphrase_command(fc_prompt, ssl_is_server_start, fc_buf, fc_size, false);
#endif
}
#ifdef FDDGM
/*
 * 使用 ssl_encpassphrase_command 的口令收集回调
 */
static int fc_tlcp_enc_external_passwd_cb(char *fc_buf, int fc_size, int fc_rwflag, void *fc_userdata)
{
	/* 与 OpenSSL 内部使用的提示相同 */
	const char *fc_prompt = "Enter PEM pass phrase:";

	Assert(fc_rwflag == 0);

	return run_ssl_passphrase_command(fc_prompt, ssl_is_server_start, fc_buf, fc_size,  true);
}

#endif

/*
 * 虚拟口令回调
 *
 * 如果 OpenSSL 被告知使用受口令保护的服务器密钥，默认情况下
 * 它会在 /dev/tty 上发出提示并尝试从那里读取密钥。
 * 这在 postmaster SIGHUP 周期期间并不好，更不用说在 EXEC_BACKEND postmaster 子进程中重新加载 SSL 上下文了。
 * 所以用这个虚拟函数覆盖它，该函数仅返回一个空的口令，确保失败。
 */
static int fc_dummy_ssl_passwd_cb(char *fc_buf, int fc_size, int fc_rwflag, void *fc_userdata)
{
	/* 设置标志以更改我们报告的错误消息 */
	dummy_ssl_passwd_cb_called = true;
	/* 并返回空字符串 */
	Assert(fc_size > 0);
	fc_buf[0] = '\0';
	return 0;
}

/*
 * 证书验证回调
 *
 * 该回调允许我们在
 * 验证过程中记录中间问题，但现在我们将看看最终错误消息
 * 是否包含足够的信息。
 *
 * 该回调还允许我们覆盖默认的接受标准
 * （例如，接受自签名或过期的证书），但
 * 目前我们接受默认检查。
 */
static int fc_verify_cb(int fc_ok, X509_STORE_CTX *fc_ctx)
{
	return fc_ok;
}

/*
 * 该回调用于将 SSL 信息消息复制到
 * PostgreSQL 日志中。
 */
static void fc_info_cb(const SSL *fc_ssl, int fc_type, int fc_args)
{
	const char *fc_desc;

	fc_desc = SSL_state_string_long(fc_ssl);

	switch (fc_type)
	{
		case SSL_CB_HANDSHAKE_START:
			ereport(DEBUG4,
					(errmsg_internal("SSL: handshake start: \"%s\"", fc_desc)));
			break;
		case SSL_CB_HANDSHAKE_DONE:
			ereport(DEBUG4,
					(errmsg_internal("SSL: handshake done: \"%s\"", fc_desc)));
			break;
		case SSL_CB_ACCEPT_LOOP:
			ereport(DEBUG4,
					(errmsg_internal("SSL: accept loop: \"%s\"", fc_desc)));
			break;
		case SSL_CB_ACCEPT_EXIT:
			ereport(DEBUG4,
					(errmsg_internal("SSL: accept exit (%d): \"%s\"", fc_args, fc_desc)));
			break;
		case SSL_CB_CONNECT_LOOP:
			ereport(DEBUG4,
					(errmsg_internal("SSL: connect loop: \"%s\"", fc_desc)));
			break;
		case SSL_CB_CONNECT_EXIT:
			ereport(DEBUG4,
					(errmsg_internal("SSL: connect exit (%d): \"%s\"", fc_args, fc_desc)));
			break;
		case SSL_CB_READ_ALERT:
			ereport(DEBUG4,
					(errmsg_internal("SSL: read alert (0x%04x): \"%s\"", fc_args, fc_desc)));
			break;
		case SSL_CB_WRITE_ALERT:
			ereport(DEBUG4,
					(errmsg_internal("SSL: write alert (0x%04x): \"%s\"", fc_args, fc_desc)));
			break;
	}
}

/*
 * 设置 DH 参数以生成临时 DH 密钥。
 * DH 参数的计算可能需要较长时间，因此必须
 * 预先计算。
 *
 * 由于很少有网站会费心创建参数文件，我们还提供了
 * OpenSSL 项目提供的参数的后备方案。
 *
 * 这些值可以是静态的（加载或计算后），因为
 * OpenSSL 库可以有效地从提供的信息中生成随机密钥。
 */
static bool fc_initialize_dh(SSL_CTX *fc_context, bool fc_isServerStart)
{
	DH		   *fc_dh = NULL;

	SSL_CTX_set_options(fc_context, SSL_OP_SINGLE_DH_USE);

	if (ssl_dh_params_file[0])
		fc_dh = fc_load_dh_file(ssl_dh_params_file, fc_isServerStart);
	if (!fc_dh)
		fc_dh = fc_load_dh_buffer(FILE_DH2048, sizeof(FILE_DH2048));
	if (!fc_dh)
	{
		ereport(fc_isServerStart ? FATAL : LOG,
				(errcode(ERRCODE_CONFIG_FILE_ERROR),
				 errmsg("DH: could not load DH parameters")));
		return false;
	}

	if (SSL_CTX_set_tmp_dh(fc_context, fc_dh) != 1)
	{
		ereport(fc_isServerStart ? FATAL : LOG,
				(errcode(ERRCODE_CONFIG_FILE_ERROR),
				 errmsg("DH: could not set DH parameters: %s",
						fc_SSLerrmessage(ERR_get_error()))));
		DH_free(fc_dh);
		return false;
	}

	DH_free(fc_dh);
	return true;
}

/*
 * 设置 ECDH 参数以生成临时椭圆曲线 DH
 * 密钥。这比 DH 参数简单得多，因为我们只需
 * 向 OpenSSL 提供曲线的名称。
 */
static bool fc_initialize_ecdh(SSL_CTX *fc_context, bool fc_isServerStart)
{
#ifndef OPENSSL_NO_ECDH
	EC_KEY	   *fc_ecdh;
	int			fc_nid;

	fc_nid = OBJ_sn2nid(SSLECDHCurve);
	if (!fc_nid)
	{
		ereport(fc_isServerStart ? FATAL : LOG,
				(errcode(ERRCODE_CONFIG_FILE_ERROR),
				 errmsg("ECDH: unrecognized curve name: %s", SSLECDHCurve)));
		return false;
	}

	fc_ecdh = EC_KEY_new_by_curve_name(fc_nid);
	if (!fc_ecdh)
	{
		ereport(fc_isServerStart ? FATAL : LOG,
				(errcode(ERRCODE_CONFIG_FILE_ERROR),
				 errmsg("ECDH: could not create key")));
		return false;
	}

	SSL_CTX_set_options(fc_context, SSL_OP_SINGLE_ECDH_USE);
	SSL_CTX_set_tmp_ecdh(fc_context, fc_ecdh);
	EC_KEY_free(fc_ecdh);
#endif

	return true;
}

/*
 * 获取通过的 SSL 错误码的原因字符串
 *
 * ERR_get_error() 被调用者用于获取错误码以传递到此处。
 *
 * 这里需要一些谨慎，因为 ERR_reason_error_string 会返回 NULL
 * 如果它不识别错误代码，或者（在 OpenSSL >= 3）如果该代码
 * 表示系统 errno 值。我们永远不想返回 NULL。
 */
static const char * fc_SSLerrmessage(unsigned long fc_ecode)
{
	const char *fc_errreason;
	static char fc_errbuf[36];

	if (fc_ecode == 0)
		return _("no SSL error reported");
	fc_errreason = ERR_reason_error_string(fc_ecode);
	if (fc_errreason != NULL)
		return fc_errreason;

	/*
	 * 在 OpenSSL 3.0.0 及以后的版本中，ERR_reason_error_string 不再映射系统
	 * errno 值。（请参阅 OpenSSL 源代码以了解解释。）
	 * 我们可以用这段代码来弥补这个缺陷。较旧的 OpenSSL
	 * 版本没有 ERR_SYSTEM_ERROR 宏，但这没关系，因为
	 * 它们也没有这个缺陷。
	 */
#ifdef ERR_SYSTEM_ERROR
	if (ERR_SYSTEM_ERROR(ecode))
		return strerror(ERR_GET_REASON(ecode));
#endif

	/* 别无选择，只能报告数值错误代码 */
	snprintf(fc_errbuf, sizeof(fc_errbuf), _("SSL error code %lu"), fc_ecode);
	return fc_errbuf;
}

int be_tls_get_cipher_bits(Port *fc_port)
{
	int			fc_bits;

	if (fc_port->ssl)
	{
		SSL_get_cipher_bits(fc_port->ssl, &fc_bits);
		return fc_bits;
	}
	else
		return 0;
}

const char * be_tls_get_version(Port *fc_port)
{
	if (fc_port->ssl)
		return SSL_get_version(fc_port->ssl);
	else
		return NULL;
}

const char * be_tls_get_cipher(Port *fc_port)
{
	if (fc_port->ssl)
		return SSL_get_cipher(fc_port->ssl);
	else
		return NULL;
}

void be_tls_get_peer_subject_name(Port *fc_port, char *fc_ptr, size_t fc_len)
{
	if (fc_port->peer)
		strlcpy(fc_ptr, fc_X509_NAME_to_cstring(X509_get_subject_name(fc_port->peer)), fc_len);
	else
		fc_ptr[0] = '\0';
}

void be_tls_get_peer_issuer_name(Port *fc_port, char *fc_ptr, size_t fc_len)
{
	if (fc_port->peer)
		strlcpy(fc_ptr, fc_X509_NAME_to_cstring(X509_get_issuer_name(fc_port->peer)), fc_len);
	else
		fc_ptr[0] = '\0';
}

void be_tls_get_peer_serial(Port *fc_port, char *fc_ptr, size_t fc_len)
{
	if (fc_port->peer)
	{
		ASN1_INTEGER *fc_serial;
		BIGNUM	   *fc_b;
		char	   *fc_decimal;

		fc_serial = X509_get_serialNumber(fc_port->peer);
		fc_b = ASN1_INTEGER_to_BN(fc_serial, NULL);
		fc_decimal = BN_bn2dec(fc_b);

		BN_free(fc_b);
		strlcpy(fc_ptr, fc_decimal, fc_len);
		OPENSSL_free(fc_decimal);
	}
	else
		fc_ptr[0] = '\0';
}

#if defined(HAVE_X509_GET_SIGNATURE_NID) || defined(HAVE_X509_GET_SIGNATURE_INFO)
char * be_tls_get_certificate_hash(Port *fc_port, size_t *fc_len)
{
	X509	   *fc_server_cert;
	char	   *fc_cert_hash;
	const EVP_MD *fc_algo_type = NULL;
	unsigned char fc_hash[EVP_MAX_MD_SIZE];	/* SHA-512 的大小 */
	unsigned int fc_hash_size;
	int			fc_algo_nid;

	*fc_len = 0;
	fc_server_cert = SSL_get_certificate(fc_port->ssl);
	if (fc_server_cert == NULL)
		return NULL;

	/*
	 * 获取证书的签名算法，以确定结果使用的哈希
	 * 算法。优先使用 X509_get_signature_info()，
	 * 该函数在 OpenSSL 1.1.1 中引入，可以处理 RSA-PSS 签名。
	 */
#if HAVE_X509_GET_SIGNATURE_INFO
	if (!X509_get_signature_info(fc_server_cert, &fc_algo_nid, NULL, NULL, NULL))
#else
	if (!OBJ_find_sigid_algs(X509_get_signature_nid(server_cert),
							 &algo_nid, NULL))
#endif
		elog(ERROR, "could not determine server certificate signature algorithm");

	/*
	 * 如果 TLS 服务器的证书签名算法为 MD5 或 SHA-1，根据 RFC 5929
	 * (https://tools.ietf.org/html/rfc5929#section-4.1) 需要使用 SHA-256 对
	 * 证书字节进行哈希处理。如果使用其他算法，则使用与签名算法相同的哈希。
	 */
	switch (fc_algo_nid)
	{
		case NID_md5:
		case NID_sha1:
			fc_algo_type = EVP_sha256();
			break;
		default:
			fc_algo_type = EVP_get_digestbynid(fc_algo_nid);
			if (fc_algo_type == NULL)
				elog(ERROR, "could not find digest for NID %s",
					 OBJ_nid2sn(fc_algo_nid));
			break;
	}

	/* 生成和保存证书哈希 */
	if (!X509_digest(fc_server_cert, fc_algo_type, fc_hash, &fc_hash_size))
		elog(ERROR, "could not generate server certificate hash");

	fc_cert_hash = palloc(fc_hash_size);
	memcpy(fc_cert_hash, fc_hash, fc_hash_size);
	*fc_len = fc_hash_size;

	return fc_cert_hash;
}
#endif

/*
 * 将 X509 主体名称转换为 cstring。
 *
 */
static char * fc_X509_NAME_to_cstring(X509_NAME *fc_name)
{
	BIO		   *fc_membuf = BIO_new(BIO_s_mem());
	int			fc_i,
				fc_nid,
				fc_count = X509_NAME_entry_count(fc_name);
	X509_NAME_ENTRY *fc_e;
	ASN1_STRING *fc_v;
	const char *fc_field_name;
	size_t		fc_size;
	char		fc_nullterm;
	char	   *fc_sp;
	char	   *fc_dp;
	char	   *fc_result;

	if (fc_membuf == NULL)
		ereport(ERROR,
				(errcode(ERRCODE_OUT_OF_MEMORY),
				 errmsg("could not create BIO")));

	(void) BIO_set_close(fc_membuf, BIO_CLOSE);
	for (fc_i = 0; fc_i < fc_count; fc_i++)
	{
		fc_e = X509_NAME_get_entry(fc_name, fc_i);
		fc_nid = OBJ_obj2nid(X509_NAME_ENTRY_get_object(fc_e));
		if (fc_nid == NID_undef)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("could not get NID for ASN1_OBJECT object")));
		fc_v = X509_NAME_ENTRY_get_data(fc_e);
		fc_field_name = OBJ_nid2sn(fc_nid);
		if (fc_field_name == NULL)
			fc_field_name = OBJ_nid2ln(fc_nid);
		if (fc_field_name == NULL)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("could not convert NID %d to an ASN1_OBJECT structure", fc_nid)));
		BIO_printf(fc_membuf, "/%s=", fc_field_name);
		ASN1_STRING_print_ex(fc_membuf, fc_v,
							 ((ASN1_STRFLGS_RFC2253 & ~ASN1_STRFLGS_ESC_MSB)
							  | ASN1_STRFLGS_UTF8_CONVERT));
	}

	/* 确保 BIO 内容的空终止 */
	fc_nullterm = '\0';
	BIO_write(fc_membuf, &fc_nullterm, 1);
	fc_size = BIO_get_mem_data(fc_membuf, &fc_sp);
	fc_dp = pg_any_to_server(fc_sp, fc_size - 1, PG_UTF8);

	fc_result = pstrdup(fc_dp);
	if (fc_dp != fc_sp)
		pfree(fc_dp);
	if (BIO_free(fc_membuf) != 1)
		elog(ERROR, "could not free OpenSSL BIO structure");

	return fc_result;
}

/*
 * 将 TLS 协议版本 GUC 枚举转换为 OpenSSL 值
 *
 * 这是一个一对一的映射，但这样做使得
 * guc.c 与 OpenSSL 的可用性和版本无关。
 *
 * 如果传递的版本不被当前的 OpenSSL
 * 版本支持，则返回 -1。如果返回非负值，
 * 后续代码可以假设它正在使用受支持的版本。
 *
 * 注意：这与 libpq 在 fe-secure-openssl.c 中的例程相当相似，
 * 因此如果更改此例程，请确保同时更新两个例程。
 */
static int fc_ssl_protocol_version_to_openssl(int fc_v)
{
	switch (fc_v)
	{
		case PG_TLS_ANY:
			return 0;
		case PG_TLS1_VERSION:
			return TLS1_VERSION;
		case PG_TLS1_1_VERSION:
#ifdef TLS1_1_VERSION
			return TLS1_1_VERSION;
#else
			break;
#endif
		case PG_TLS1_2_VERSION:
#ifdef TLS1_2_VERSION
			return TLS1_2_VERSION;
#else
			break;
#endif
		case PG_TLS1_3_VERSION:
#ifdef TLS1_3_VERSION
			return TLS1_3_VERSION;
#else
			break;
#endif
	}

	return -1;
}

/*
 * 同样提供与字符串的映射。
 */
static const char * fc_ssl_protocol_version_to_string(int fc_v)
{
	switch (fc_v)
	{
		case PG_TLS_ANY:
			return "any";
		case PG_TLS1_VERSION:
			return "TLSv1";
		case PG_TLS1_1_VERSION:
			return "TLSv1.1";
		case PG_TLS1_2_VERSION:
			return "TLSv1.2";
		case PG_TLS1_3_VERSION:
			return "TLSv1.3";
	}

	return "(unrecognized)";
}


static void fc_default_openssl_tls_init(SSL_CTX *fc_context, bool fc_isServerStart)
{
	if (fc_isServerStart)
	{
		if (ssl_passphrase_command[0])
			SSL_CTX_set_default_passwd_cb(fc_context, fc_ssl_external_passwd_cb);
	}
	else
	{
		if (ssl_passphrase_command[0] && ssl_passphrase_command_supports_reload)
			SSL_CTX_set_default_passwd_cb(fc_context, fc_ssl_external_passwd_cb);
		else

			/*
			 * 如果正在重载且未配置外部命令，则覆盖
			 * OpenSSL 对密码保护文件的默认处理，
			 * 因为我们不希望在已运行的服务器中提示输入密码。
			 */
			SSL_CTX_set_default_passwd_cb(fc_context, fc_dummy_ssl_passwd_cb);
	}
}

#ifdef FDDGM
static void fc_default_openssl_tlcp_enc_init(SSL_CTX *fc_context, bool fc_isServerStart)
{
	if (fc_isServerStart)
	{
		if (ssl_encpassphrase_command[0])
			SSL_CTX_set_default_passwd_cb(fc_context, fc_tlcp_enc_external_passwd_cb);
	}
	else
	{
		if (ssl_encpassphrase_command[0] && ssl_passphrase_command_supports_reload)
			SSL_CTX_set_default_passwd_cb(fc_context, fc_tlcp_enc_external_passwd_cb);
		else

			/*
			 * 如果正在重载且未配置外部命令，则覆盖
			 * OpenSSL 对密码保护文件的默认处理，
			 * 因为我们不希望在已运行的服务器中提示输入密码。
			 */
			SSL_CTX_set_default_passwd_cb(fc_context, fc_dummy_ssl_passwd_cb);
	}
}

#endif

